﻿using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.Text;

namespace Apex.ASPNETCore.GlobalException
{
    public class GlobalMiddleWare
    {
        private readonly RequestDelegate _next;
        private readonly ILogger<GlobalMiddleWare> logger;

        public GlobalMiddleWare(RequestDelegate next, ILogger<GlobalMiddleWare> logger)
        {
            _next = next;
            this.logger = logger;
        }

        public async Task Invoke(HttpContext context)
        {

            #region 读取请求内容
            // 启用请求体的缓冲
            context.Request.EnableBuffering();
            // 读取请求体
            string requestBody = await new StreamReader(context.Request.Body).ReadToEndAsync();
            context.Request.Body.Position = 0;
            // 查询参数
            string queryString = Uri.UnescapeDataString(context.Request.QueryString.Value);
            #endregion
            // 报错捕获到的报错信息
            //Exception exception = null;

            // 创建一个内存流作为 Response.Body 的替代
            var originalBodyStream = context.Response.Body;
            using (var responseBody = new MemoryStream())
            {
                // 将内存流设置为新的 Response.Body
                context.Response.Body = responseBody;
                try
                {
                    //调用管道执行下一个中间件
                    await _next(context);
                }
                catch (Exception ex)
                {
                    string errorMsg = ex != null ? $"Message:{ex.Message}\nStackTrace:{ex.StackTrace}" : "";
                    context.Response.ContentType = "application/json";
                    context.Response.StatusCode = 500;
                    await context.Response.WriteAsync(errorMsg, Encoding.UTF8);
                }
                finally
                {
                    string responseBodyContent = string.Empty;
                    // 如果有报错
                    if (context.Response.StatusCode != 200)
                    {
                        // 转成指定异常格式、输出日志
                        responseBodyContent = await HandleException(context, logger, requestBody, queryString);
                    }
                    // 没有报错继续按顺序执行
                    else
                    {
                        responseBodyContent = await HandleNonException(context);
                    }
                    // 设置响应的内容类型为 JSON
                    context.Response.ContentType = "application/json";
                    // 将 Response.Body 恢复为原始的流
                    context.Response.Body = originalBodyStream;
                    // 将内存流的内容写回原始的 Response.Body
                    await context.Response.WriteAsync(responseBodyContent);

                }
            }
        }

        public static async Task<string> HandleException(HttpContext context, ILogger logger, string requestBody, string queryString)
        {
            // 其它请求信息
            var requestPath = context.Request.Path; // 请求路径
            var requestMethod = context.Request.Method; // 请求方法
            var userName = context.User.Identity?.Name ?? "Anonymous"; // 用户

            // 将内存流的内容读取为字符串
            string responseBodyContent = await ReadResponseBody(context.Response);

            // 不是特定的报错信息
            if (!responseBodyContent.Contains("Key") && !responseBodyContent.Contains("Error"))
            {
                // 将报错信息转成特定格式
                responseBodyContent = CreateErrorResponseHelper.CreateErrorResponse(context.Response.StatusCode, responseBodyContent).ToJsonString();
            }

            // 输出日志
            logger.LogInterpolatedError($"\nError occurred on path '{requestPath}' with method '{requestMethod}' \nQuery: '{queryString}' \nBody: '{requestBody}' \nUser: '{userName}' \nException: {responseBodyContent}");
            return responseBodyContent;
        }

        public static async Task<string> HandleNonException(HttpContext context)
        {
            // 将内存流的内容读取为字符串
            string responseBodyContent = await ReadResponseBody(context.Response);
            return responseBodyContent;
        }

        private static async Task<string> ReadResponseBody(HttpResponse response)
        {
            response.Body.Seek(0, SeekOrigin.Begin);
            using (var reader = new StreamReader(response.Body))
            {
                return await reader.ReadToEndAsync();
            }
        }
    }
}